package com.oplink.webems.common.snmp.base;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import org.apache.log4j.Logger;
import org.snmp4j.CommunityTarget;
import org.snmp4j.PDU;
import org.snmp4j.ScopedPDU;
import org.snmp4j.Snmp;
import org.snmp4j.Target;
import org.snmp4j.TransportMapping;
import org.snmp4j.UserTarget;
import org.snmp4j.event.ResponseEvent;
import org.snmp4j.mp.MPv3;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.security.SecurityModels;
import org.snmp4j.security.SecurityProtocols;
import org.snmp4j.security.USM;
import org.snmp4j.security.UsmUser;
import org.snmp4j.smi.GenericAddress;
import org.snmp4j.smi.Integer32;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.Variable;
import org.snmp4j.smi.VariableBinding;
import org.snmp4j.transport.DefaultUdpTransportMapping;

import com.oplink.webems.common.snmp.security.CommBaseSecurity;
import com.oplink.webems.common.snmp.security.SnmpV1V2Community;
import com.oplink.webems.common.snmp.security.SnmpV3Security;

public class SnmpApi extends Communication implements ISnmp {
	private Snmp snmp;
	private Target target;
	
	static Logger l = Logger.getLogger(SnmpApi.class);
	
	public final static Set<String> errorResults = new HashSet<String>(){
		private static final long serialVersionUID = -305301987511866360L;
		{
			add(null);
			add("noSuchInstance");
			add("noSuchObject");
		}
	};
	
	
	public final static Map<Integer, String> errorCode = new HashMap<Integer, String>(){
		private static final long serialVersionUID = -4356284995931110373L;

		{
			put(16,"SNMP_ERROR_AUTHORIZATION_ERROR");
			put(3,"SNMP_ERROR_BAD_VALUE");
			put(14,"SNMP_ERROR_COMMIT_FAILED");
			put(5,"SNMP_ERROR_GENERAL_ERROR");
			put(18,"SNMP_ERROR_INCONSISTENT_NAME");
			put(12,"SNMP_ERROR_INCONSISTENT_VALUE");
			put(-4,"SNMP_ERROR_IO");
			put(-2,"SNMP_ERROR_LEXICOGRAPHIC_ORDER");
			put(6,"SNMP_ERROR_NO_ACCESS");
			put(11,"SNMP_ERROR_NO_CREATION");
			put(2,"SNMP_ERROR_NO_SUCH_NAME");
			put(17,"SNMP_ERROR_NOT_WRITEABLE");
			put(4,"SNMP_ERROR_READ_ONLY");
			put(-3,"SNMP_ERROR_REPORT");
			put(13,"SNMP_ERROR_RESOURCE_UNAVAILABLE");
			put(0,"SNMP_ERROR_SUCCESS");
			put(-1,"SNMP_ERROR_TIMEOUT");
			put(1,"SNMP_ERROR_TOO_BIG");
			put(15,"SNMP_ERROR_UNDO_FAILED");
			put(9,"SNMP_ERROR_WRONG_ENCODING");
			put(8,"SNMP_ERROR_WRONG_LENGTH");
			put(7,"SNMP_ERROR_WRONG_TYPE");
			put(10,"SNMP_ERROR_WRONG_VALUE");
		}
	};
	
	public SnmpApi(String ip) throws SnmpException{
		super(ip, new SnmpV1V2Community("public","public"), 161, 1500, 1, SnmpConstants.version2c);
		init();
	}
	
	public SnmpApi(String _ip, CommBaseSecurity _community, int _port, int _timeout, int _retry, int _version){
		super(_ip, _community, _port, _timeout, _retry, _version);
		init();
	}
	
	private void init(){
		@SuppressWarnings("rawtypes")
		TransportMapping transport;
		try {
			transport = new DefaultUdpTransportMapping();
			snmp = new Snmp(transport);
			if(this.getVersion() == SnmpConstants.version3){
				// v3
				USM usm = new USM(  SecurityProtocols.getInstance(),   
						new OctetString(MPv3.createLocalEngineID()), 
						0
						);   
				SecurityModels.getInstance().addSecurityModel(usm); 
				
				SnmpV3Security comm = (SnmpV3Security) this.getCommunity();
				snmp.getUSM().addUser(  new OctetString(comm.getUserName()),   
						new UsmUser(new OctetString(comm.getUserName()),   
								new OID(comm.getAuth()),   
								new OctetString(comm.getAuthPassword()),   
								new OID(comm.getPrivacy()),   
								new OctetString(comm.getPrivacyPassword()))
						); 
				target = new UserTarget();
				((UserTarget)target).setSecurityLevel(comm.getSecurityLevel());   
				((UserTarget)target).setSecurityName(new OctetString(comm.getUserName()));
				
				
			}else{
				target = new CommunityTarget();
			}
			
			target.setAddress(GenericAddress.parse("udp:"+this.getIp()+"/"+this.getPort()));
			target.setRetries(getRetry());
			target.setTimeout(getTimeout());
			target.setVersion(getVersion());
			transport.listen();  
		} catch (IOException e) {
			//如果该处发生异常，则没有办法可处理，只能直接打印出异常。抛给上层代码，上层代码也无法处理。
			//若发生该异常，则可能是系统级的，整个通讯模块无法使用。
			e.printStackTrace();
		}
	}
	
	
	/**
	 * 通讯基础方法
	 * @param type
	 * @param address
	 * @return
	 * @throws SnmpException
	 */
	private Map<String, Variable> send(int type, VariableBinding... address) throws SnmpException{
		// init PDU and confirm Target
		PDU pdu = new PDU();
		if(this.getVersion() == SnmpConstants.version3){
			pdu = new ScopedPDU();
		}else{
			SnmpV1V2Community comm = (SnmpV1V2Community) this.getCommunity();
			if(type == PDU.SET){
				((CommunityTarget)target).setCommunity(new OctetString(comm.getWriteComm()));
			}else{
				((CommunityTarget)target).setCommunity(new OctetString(comm.getReadComm()));
			}
		}
		//fit PDU
		for(VariableBinding add : address){
			pdu.add(add);
		}
		//set PDU type
		pdu.setType(type);
		//send
		try {
			ResponseEvent ret = snmp.send(pdu, target);
			if(ret==null || ret.getResponse()==null){
				throw new SnmpException(SnmpException.COMM_HASNORESPONSE, this.ip);
			}else{
				int errorStatus = ret.getResponse().getErrorStatus();
				if(errorStatus!=0){
					if(type == PDU.SET){
						throw new SnmpException(SnmpException.COMM_WRITE_ERROR,this.ip,address[0].getOid().toString(),address[0].getVariable().toString(),errorCode.get(errorStatus));
					}else{
						throw new SnmpException(SnmpException.COMM_READ_ERROR,this.ip,address[0].getOid().toString(),null,errorCode.get(errorStatus));
					}
					
				}else{
					return responseEventToMap(ret);
				}
			}
		} catch (IOException e) {
			throw new SnmpException(SnmpException.COMM_SYS_ERROR,this.ip);
		}
	}
	
	@SuppressWarnings("unchecked")
	private Map<String, Variable> responseEventToMap(ResponseEvent re) throws SnmpException{
		Map<String, Variable> ret = new HashMap<String, Variable>();
		
		Vector<VariableBinding> vs = (Vector<VariableBinding>) re.getResponse().getVariableBindings();
		for(VariableBinding v : vs){
			String oid = v.getOid().toString();
			if(v.getVariable() == null || errorResults.contains(v.getVariable().toString())){
				throw new SnmpException(SnmpException.COMM_READ_ERROR, this.ip, oid, v.getVariable().toString(),v.getVariable().toString());
			}else{
				ret.put(oid, v.getVariable());
			}
		}
		
		return ret;
	}
	@Override
	public String get(String address) throws SnmpException {
		Map<String, Variable> map = this.send(PDU.GET , new VariableBinding(new OID(address)));
		Variable ret = map.get(address);
		if(ret != null){
			return ret.toString();
		}
		return null;
	}

	@Override
	public List<String> get(List<String> addressList) throws SnmpException {
		VariableBinding[] addArray = new VariableBinding[addressList.size()];
		for(int i=0; i<addressList.size(); i++){
			addArray[i] = new VariableBinding(new OID(addressList.get(i)));
		}
		Map<String, Variable> map = this.send(PDU.GET , addArray);
		List<String> ret = new ArrayList<String>();
		for(String add : addressList){
			Variable v = map.get(add);
			if(v==null){
				ret.add(null);
			}else{
				ret.add(v.toString());
			}
		}
		return ret;
	}
	@Override
	public String[] getNext(String address) throws SnmpException {
		Map<String, Variable> map = this.send(PDU.GETNEXT , new VariableBinding(new OID(address)));
		String keythis = null;
		for(String key : map.keySet()){
			keythis = key;
		}
		return new String[]{keythis, map.get(keythis)==null?null:map.get(keythis).toString()};
	}
	@Override
	public Map<String, String> walk(String address) throws SnmpException {
		throw new SnmpException("not support the method",this.ip);
	}
	@Override
	public List<Vector<String>> table(String tableHeader) throws SnmpException {
		throw new SnmpException("not support the method",this.ip);
	}
	@Override
	public List<Boolean> set(List<Vector<String>> params)throws SnmpException {
		List<Boolean> ret = new ArrayList<Boolean>();
		for(Vector<String> p : params){
			String key = p.elementAt(0);
			String value = p.elementAt(1);
			boolean aRet = this.set(key, value);
			ret.add(aRet);
		}
		return ret;
	}

	@Override
	public boolean set(String address, String value) throws SnmpException {
		Map<String, Variable> map = this.send(PDU.SET , new VariableBinding(new OID(address), getSetValue(address, value)));
		//是否可以通过返回的response判断set操作是否成功
		return value.equals(String.valueOf(map.get(address)));
	}
	
	public Variable getSetValue(String address, String value){
		Variable v = null;
		/*if(address.equals("Integer32")){
			v = new Integer32(Integer.parseInt(value));
		}else if(address.equals("OctetString")){
			v = new OctetString(value);
		}else if(address.equals("timeTicks")){
			v = new TimeTicks(Long.parseLong(value));
		}else if(address.equals("ipAddress")){
			v = new IpAddress(value);
		}else{
			v = new Integer32(Integer.parseInt(value));
		}*/
		try{
			v = new Integer32(Integer.parseInt(value));
		}catch(Exception e){
			v = new OctetString(value);
		}
		return v;
	}
	
	public void close(){
		try {
			snmp.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args){
		try {
			
			//CommBaseSecurity security = new SnmpV3Security("yongzeng", AuthMD5.ID.toString(), PrivDES.ID.toString(), "asdfasdfas", "asdfasfdas", SecurityLevel.NOAUTH_NOPRIV);
			CommBaseSecurity security = new SnmpV1V2Community("Admin@123", "Admin@123");
			SnmpApi sa = new SnmpApi("172.16.166.232", security, 161, 1500, 1, SnmpConstants.version2c);
			String address = "1.3.6.1.4.1.2011.2.25.100.5.1.1.1.1.1.5.1";
			String address1 = "1.3.6.1.4.1.2011.2.25.100.5.1.1.1.2.1.5.1";
			/*String value = "10";
			String value1 = "1";
			Vector<String> p1 = new Vector<String>();
			p1.add(address);
			p1.add(value);
			Vector<String> p2 = new Vector<String>();
			p2.add(address1);
			p2.add(value1);
			
			List<Vector<String>> list = new ArrayList<Vector<String>>();
			list.add(p2);
			list.add(p1);*/
			
			List<String> listOid = new ArrayList<String>();
			listOid.add(address);
			listOid.add(address1);
			
			System.out.println(sa.get(listOid));
			
			System.out.println(sa.get("1.3.6.1.4.1.2011.2.25.100.5.1.1.1.141.1.5.1"));
			
			System.out.println(Arrays.asList(sa.getNext("1.3.6.1.2.1.1.1")));
		} catch (SnmpException e) {
			e.printStackTrace();
		}
		
	}
}
